package cn.njxzc.demo.bam;

import java.util.Arrays;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Bank implements BankService {

	/**
	 * meaning of the variable "level": -1 ------------>quit the system. 0
	 * ------------>main menu:1.Sign in 2.Create Account 1 ------------>account
	 * management service:1.deposit 2.withdraw 3.reset ceiling(only credit
	 * account)
	 */
	// Account array initial size.
	private final int ARR_INIT_SIZE = 10;
	// When the array is full.Expand it 10 once a time.
	private final int ARR_INCREMENT = 10;

	private final int ACCOUNT_TYPE_SAVING = 1;
	private final int ACCOUNT_TYPE_CREDIT = 2;
	// Array to save informations of all accounts.
	private Account[] accounts;
	// the count of the accounts.
	private int accountNum;
	// a regex to check pure number.
	private String numberRegex = "^[0-9]+$";
	// a regex to check the number that represents money.
	private String moneyRegex = "^[0-9]+|[0-9]+\\.[0-9]+$";
	// a character which means quiting the system.
	private static String QUIT = "q";
	// The character represents back to previous level.
	private static String BACK = "b";
	// The character represents back to previous step.
	private static String PREVIOUS_STEP = "p";
	// The character represents sign out and then back to the main menu.
	private static String LOGOUT = "o";

	public Bank() {
		accounts = new Account[ARR_INIT_SIZE];
	}

	public Account createAccount(long id, String password,
			String passwordAgain, String realName, String personId,
			String email, int accountType) {
		Account account = null;
		if (password.equals(passwordAgain)) {
			if (accountType == ACCOUNT_TYPE_SAVING) {
				account = new SavingAccount(id, password, realName, personId);
			} else if (accountType == ACCOUNT_TYPE_CREDIT) {
				account = new CreditAccount(id, password, realName, personId);
			}
			account.setEmail(email);
			if (accountNum >= ARR_INIT_SIZE) {
				accounts = Arrays.copyOf(accounts, accountNum + ARR_INCREMENT);
			}
			accounts[accountNum] = account;
			accountNum++;
		}
		return account;
	}

	// override the method above.
	public Account createAccount(long id, String password, String realName,
			String personId, String email, int accountType) {
		Account account = null;
		if (accountType == ACCOUNT_TYPE_SAVING) {
			account = new SavingAccount(id, password, realName, personId);
		} else if (accountType == ACCOUNT_TYPE_CREDIT) {
			account = new CreditAccount(id, password, realName, personId);
		}
		account.setEmail(email);
		if (accountNum >= ARR_INIT_SIZE) {
			accounts = Arrays.copyOf(accounts, accountNum + ARR_INCREMENT);
		}
		accounts[accountNum] = account;
		accountNum++;
		return account;
	}

	public Account login(long id, String password) {
		Account account;
		account = findAccountById(id);
		if (account != null && !account.getPassword().equals(password)) {
			account = null;
		}
		return account;
	}

	public Account deposit(long id, double money) {
		int index = getAccountIndexById(id);
		accounts[index].deposit(money);
		return accounts[index];
	}

	/**
	 * Because the account has two types.So,when users withdraw, we should take
	 * two different ways to handle it.
	 * 
	 * @param id
	 * @param money
	 * @return Account
	 */
	public Account withdraw(long id, double money) {
		int index = getAccountIndexById(id);
		if (accounts[index] instanceof CreditAccount) {
			((CreditAccount) accounts[index]).withdraw(money);
		} else {
			accounts[index].withdraw(money);
		}
		return accounts[index];
	}

	public Account setCeiling(long id, double ceiling) {
		int index = getAccountIndexById(id);
		/*
		 * if(accounts[index] instanceof CreditAccount){
		 * ((CreditAccount)accounts[index]).setCeiling(ceiling); return
		 * accounts[index]; } return null;
		 */
		((CreditAccount) accounts[index]).setCeiling(ceiling);
		return accounts[index];
	}

	/**
	 * Calculate sum of all accounts' balances.
	 * 
	 * @return double
	 */
	public double countAllBalances() {
		double sum = 0.0;
		for (int i = 0; i < accounts.length; i++) {
			sum += ((CreditAccount) accounts[i]).getCeiling();
		}
		return sum;
	}

	/**
	 * Calculate sum of all credit accounts' ceilings.
	 * 
	 * @return double
	 */
	public double countAllCeilings() {
		double sum = 0.0;
		for (int i = 0; i < accounts.length; i++) {
			sum += accounts[i].getBalance();
		}
		return sum;
	}

	private Account findAccountById(long id) {
		Account account = null;
		int index = getAccountIndexById(id);
		if (index >= 0) {
			account = accounts[index];
		}
		return account;
	}

	/**
	 * we walk through the array,and use the parameter id to compare with every
	 * element's id in the array.If id equals one element's id ,then return this
	 * element's index.Otherwise, return -1,which means no one in this array has
	 * the id.
	 * 
	 * @param id
	 * @return int
	 */
	private int getAccountIndexById(long id) {
		int index = -1;
		for (int i = 0; i < accounts.length; i++) {
			if (id == accounts[i].getId()) {
				index = i;
				break;
			}
		}
		return index;
	}

	public long generateId() {
		return accountNum + 1;
	}

	// The array contains all tips in the "create account" service.
	private static String[] CREATE_ACCOUNT_TIPS = {
			"Please choose account type:\n1.Saving Account\t2.Credit Account",
			"Please enter your name:",
			"Please enter personal ID:",
			"Please enter E-mail address:",
			"Please enter the password that must be at least 6 characters long:",
			"Please enter password again:" };

	@Override
	public BankInfo creatAccountService() {
		BankInfo bankInfo = new BankInfo();
		String str = "('" + QUIT + "' to quit,'" + BACK
				+ "' to back to main menu,'" + PREVIOUS_STEP
				+ "' to previous step)";
		String password, passwordAgain, realName, personId, email;
		password = passwordAgain = realName = personId = email = null;
		long id = 0;
		String emailRegex = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";
		Pattern pattern = Pattern.compile(emailRegex);
		int accountType = 0;
		int nextStep = 0;
		int currentStep = 1;
		Scanner scan = new Scanner(System.in);
		String order;
		do {
			System.out.println(CREATE_ACCOUNT_TIPS[currentStep - 1]);
			System.out.println(str);
			order = scan.next();
			if (QUIT.equals(order)) {
				nextStep = -1;
				continue;
			}
			if (BACK.equals(order)) {
				nextStep = 0;
				continue;
			}
			if (PREVIOUS_STEP.equals(order)) {
				nextStep = --currentStep;
				continue;
			}
			switch (currentStep) {
			case 1:
				// choose account type
				nextStep = 1;
				if (!Pattern.matches(numberRegex, order)) {
					printInfo("Wrong input.");
					break;
				}
				accountType = Integer.valueOf(order);
				if (accountType == ACCOUNT_TYPE_SAVING
						|| accountType == ACCOUNT_TYPE_CREDIT) {
					nextStep = ++currentStep;
				} else {
					printInfo("Wrong number.");
				}
				break;
			case 2:
				// enter name
				realName = order;
				nextStep = ++currentStep;
				break;
			case 3:
				// enter ID
				personId = order;
				nextStep = ++currentStep;
				break;
			case 4:
				// enter E-mail address
				email = order;
				Matcher matcher = pattern.matcher(email);
				if (matcher.matches()) {
					nextStep = ++currentStep;
				} else {
					printInfo("Wrong E-mail address.");
				}
				break;
			case 5:
				// enter password
				password = order;
				if (password.length() >= 6) {
					nextStep = ++currentStep;
				} else {
					printInfo("The length must be at least 6.");
				}
				break;
			case 6:
				// enter password again
				passwordAgain = order;
				if (password.equals(passwordAgain)) {
					id = generateId();
					nextStep = ++currentStep;
				} else {
					nextStep = --currentStep;
					printInfo("The passwords you entered must be same.");
				}
				break;
			}

		} while (nextStep > 0 && nextStep <= CREATE_ACCOUNT_TIPS.length);

		// if variable nextStep is better than CREATE_ACCOUNT_TIPS.length,
		// it means the user has already finished the steps of creating account.
		if (nextStep > CREATE_ACCOUNT_TIPS.length) {
			Account account = createAccount(id, password, realName, personId,
					email, accountType);
			System.out.println("Create account successfully.");
			showAccountInfo(account);
			nextStep = 0;
			printInfo("Back to main menu...");
		}

		bankInfo.setNextStep(nextStep);
		return bankInfo;
	}

	// This array contains all tips in the "login" service.
	private static String[] LOGIN_TIPS = { "Please enter Account ID:",
			"Please enter password:" };

	@Override
	public BankInfo loginService() {
		Scanner scan = new Scanner(System.in);
		String order;
		BankInfo bankInfo = new BankInfo();
		Account account = null;
		long id = 0;
		String password = null;
		int nextStep = 0;
		int currentStep = 1;
		String str = "('" + QUIT + "' to quit,'" + BACK
				+ "' to back to main menu,'" + PREVIOUS_STEP
				+ "' to previous step)";
		do {
			System.out.println(LOGIN_TIPS[currentStep - 1]);
			System.out.println(str);
			order = scan.next();
			if (QUIT.equals(order)) {
				nextStep = -1;
				continue;
			}
			if (BACK.equals(order)) {
				nextStep = 0;
				continue;
			}
			if (PREVIOUS_STEP.equals(order)) {
				nextStep = --currentStep;
				continue;
			}
			switch (currentStep) {
			case 1:
				if (!Pattern.matches(numberRegex, order)) {
					printInfo("Wrong input.");
					break;
				}
				id = Long.valueOf(order);
				nextStep = ++currentStep;
				break;
			case 2:
				password = order;
				if (password.length() < 6) {
					nextStep = --currentStep;
					printInfo("Login failed!ID or password is wrong.");
					break;
				}
				account = login(id, password);
				if (account == null) {
					printInfo("Login failed!ID or password is wrong.");
					nextStep = --currentStep;
				} else {
					nextStep = ++currentStep;
				}
				break;
			}
		} while (nextStep > 0 && nextStep <= LOGIN_TIPS.length);

		if (nextStep > LOGIN_TIPS.length) {
			bankInfo.setCurrentAccount(account);
			nextStep = 1;
		}
		bankInfo.setNextStep(nextStep);
		return bankInfo;
	}

	@Override
	public BankInfo logoutService() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public BankInfo accountManagementService(Account account) {
		BankInfo bankInfo = null;
		if (account instanceof CreditAccount) {
			bankInfo = creditAccountManagementService(account);
		} else {
			bankInfo = savingAccountManagementService(account);
		}
		return bankInfo;
	}

	public BankInfo resetCeilingService(CreditAccount account) {

		BankInfo bankInfo = new BankInfo(1);
		Scanner scan = new Scanner(System.in);
		String order;
		String str = "('" + QUIT + "' to quit,'" + PREVIOUS_STEP
				+ "' to previous step)";
		printWealthInfo(account.getCeiling(), "Ceiling");
		System.out.println("Enter a ceiling:");
		System.out.println(str);
		order = scan.next();
		if (QUIT.equals(order)) {
			return new BankInfo(-1);
		}
		if (BACK.equals(order)) {
			bankInfo.setCurrentAccount(account);
			return bankInfo;
		}
		if (!Pattern.matches(moneyRegex, order)) {
			printInfo("Wrong input.");
			bankInfo.setCurrentAccount(account);
			return bankInfo;
		}
		double ceiling = Double.valueOf(order);
		account = (CreditAccount) setCeiling(account.getId(), ceiling);
		printWealthInfo(account.getCeiling(), "Ceiling");
		bankInfo.setCurrentAccount(account);
		return bankInfo;
	}

	private BankInfo creditAccountManagementService(Account account) {
		BankInfo bankInfo = new BankInfo(1, account);
		Scanner scan = new Scanner(System.in);
		String order;
		String str = "('" + QUIT + "' to quit,'" + LOGOUT + "' to sing out)";
		do {
			System.out.println("1.Deposit\t2.Withdraw\n3.Reset ceiling ");
			System.out.println(str);
			order = scan.next();
			if (QUIT.equals(order)) {
				bankInfo.setNextStep(-1);
				continue;
			}
			if (LOGOUT.equals(order)) {
				bankInfo.setNextStep(0);
				bankInfo.setCurrentAccount(null);
				continue;
			}
			if (!Pattern.matches(numberRegex, order)) {
				printInfo("Wrong input.");
			} else {
				if ("1".equals(order)) {
					bankInfo = depositService(bankInfo.getCurrentAccount());
				} else if ("2".equals(order)) {
					bankInfo = withdrawService(bankInfo.getCurrentAccount());
				} else if ("3".equals(order)) {
					bankInfo = resetCeilingService((CreditAccount) bankInfo
							.getCurrentAccount());
				} else {
					printInfo("Wrong number.");
				}
			}

		} while (bankInfo.getNextStep() > 0);

		return bankInfo;
	}

	private BankInfo depositService(Account account) {
		BankInfo bankInfo = new BankInfo(1);
		Scanner scan = new Scanner(System.in);
		String order;
		String str = "('" + QUIT + "' to quit,'" + PREVIOUS_STEP
				+ "' to previous step)";
		printWealthInfo(account.getBalance(), "Balance");
		System.out.println("How much money do you want to deposit?");
		System.out.println(str);
		order = scan.next();
		if (QUIT.equals(order)) {
			return new BankInfo(-1);
		}
		if (PREVIOUS_STEP.equals(order)) {
			bankInfo.setCurrentAccount(account);
			return bankInfo;
		}
		if (!Pattern.matches(moneyRegex, order)) {
			printInfo("Wrong input.");
			bankInfo.setCurrentAccount(account);
			return bankInfo;
		}
		double money = Double.valueOf(order);
		account = deposit(account.getId(), money);
		printWealthInfo(account.getBalance(), "Balance");
		bankInfo.setCurrentAccount(account);
		return bankInfo;
	}

	private BankInfo withdrawService(Account account) {
		BankInfo bankInfo = new BankInfo(1);
		Scanner scan = new Scanner(System.in);
		String order;
		String str = "('" + QUIT + "' to quit,'" + PREVIOUS_STEP
				+ "' to previous step)";
		printWealthInfo(account.getBalance(), "Balance");
		System.out.println("How much money do you want to withdraw?");
		System.out.println(str);
		order = scan.next();
		if (QUIT.equals(order)) {
			return new BankInfo(-1);
		}
		if (PREVIOUS_STEP.equals(order)) {
			bankInfo.setCurrentAccount(account);
			return bankInfo;
		}
		if (!Pattern.matches(moneyRegex, order)) {
			printInfo("Wrong input.");
			bankInfo.setCurrentAccount(account);
			return bankInfo;
		}
		double money = Double.valueOf(order);
		double oldBalance = account.getBalance();
		account = withdraw(account.getId(), money);
		if (oldBalance == account.getBalance()) {
			printInfo("Operate failed.Not enough money or ceiling.");
		} else {
			printWealthInfo(account.getBalance(), "Balance");
		}
		bankInfo.setCurrentAccount(account);
		return bankInfo;
	}

	private BankInfo savingAccountManagementService(Account account) {
		BankInfo bankInfo = new BankInfo(1, account);
		Scanner scan = new Scanner(System.in);
		String order;
		String str = "('" + QUIT + "' to quit,'" + LOGOUT + "' to sing out)";
		do {
			System.out.println("1.Deposit\t2.Withdraw");
			System.out.println(str);
			order = scan.next();
			if (QUIT.equals(order)) {
				bankInfo.setNextStep(-1);
				continue;
			}
			if (LOGOUT.equals(order)) {
				bankInfo.setNextStep(0);
				bankInfo.setCurrentAccount(null);
				continue;
			}
			if (!Pattern.matches(numberRegex, order)) {
				printInfo("Wrong input.");
			}
			if ("1".equals(order)) {
				bankInfo = depositService(bankInfo.getCurrentAccount());
			} else if ("2".equals(order)) {
				bankInfo = withdrawService(bankInfo.getCurrentAccount());
			} else {
				printInfo("Wrong number.");
			}
		} while (bankInfo.getNextStep() > 0);

		return bankInfo;
	}

	/**
	 * this method provides the entrance of the BAM.
	 */
	public void startService() {
		// The variable level represents user's location.
		// when level equal 0 ,it means user is on the main menu.
		// when level equal -1,it means user quit the system.
		int level = 0;

		BankInfo bankInfo = null;
		String str;
		Scanner scan = new Scanner(System.in);
		String order;
		System.out.println("Welcome to BAM.");
		do {
			switch (level) {
			case 0:
				System.out.println("1.Sign in\t2.Create account");
				System.out.println("('" + QUIT + "' to quit)");
				order = scan.next();
				if (QUIT.equals(order)) {
					level = -1;
					continue;
				}
				if (!Pattern.matches(numberRegex, order)) {
					printInfo("Wrong input.");
					break;
				}
				if ("1".equals(order)) {
					bankInfo = loginService();
				} else if ("2".equals(order)) {
					bankInfo = creatAccountService();
				} else {
					printInfo("Wrong number.");
					break;
				}
				level = bankInfo.getNextStep();
				break;
			case 1:
				bankInfo = accountManagementService(bankInfo
						.getCurrentAccount());
				level = bankInfo.getNextStep();
				break;
			}
		} while (level >= 0);
		System.out.println("Bye.");
	}

	private void printInfo(String info) {
		System.out.println(info + "\n");
	}

	// print basic info of the account.
	private void showAccountInfo(Account account) {
		System.out.println("-----------Account Info----------");
		System.out.println("Account ID:" + account.getId());
		System.out.println("Name:" + account.getRealName());
		System.out.println("Personal ID:" + account.getPersonId());
		System.out.println("E-mail:" + account.getEmail());
		System.out.print("Account Type:");
		if (account instanceof CreditAccount) {
			System.out.println("Credit Account");
		} else {
			System.out.println("Saving Account");
		}
		System.out.println("---------------------------------\n");
	}

	/**
	 * 
	 * @param wealth
	 *            balance or ceiling
	 * @param title
	 *            it could be "Balance" or "Ceiling"
	 */
	private void printWealthInfo(double wealth, String title) {
		System.out.println("*********************");
		System.out.println(title + ":" + wealth);
		System.out.println("*********************");
	}

	public void init() {
		createAccount(1, "123456", "zn", "5555555", "12345678@qq.com",
				ACCOUNT_TYPE_CREDIT);
		createAccount(2, "654321", "nz", "6666666", "87654321@qq.com",
				ACCOUNT_TYPE_SAVING);
	}

}
